﻿//     Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;

namespace Microsoft.Gateway.Utils.Update
{
    public class UpdateClient
    {
        const string DefaultUpdateService       = "https://www.service-gateway.net";
        
        public enum Components
        {
            Gateway,
            ManagementConsole,
        }

        public UpdateClient(string requestor = null, string updateServiceRoot = null)
        {
            this.Requestor = requestor;
            if (!String.IsNullOrWhiteSpace(updateServiceRoot))
            {
                this.UpdateServiceUri = updateServiceRoot;
            }
            else
            {
                this.UpdateServiceUri = DefaultUpdateService;
            }
        }

        public string UpdateServiceUri { get; set; }
        public string Requestor { get; set; }

        public static PackageManifest GetAvailableUpdate(Components componentToCheck, string requestor = null, string updateServiceRoot = null)
        {
            var client = new UpdateClient(requestor, updateServiceRoot);
            return client.GetAvailableUpdate(componentToCheck);
        }

        public PackageManifest GetAvailableUpdate(Components componentToCheck)
        {
            return GetAvailableUpdateAsync(componentToCheck).Result;
        }

        public async Task<PackageManifest> GetAvailableUpdateAsync(Components componentToCheck)
        {
            var requestUri = new ApiUriBuilder(this.UpdateServiceUri, new Dictionary<string, string>
                {
                    { ApiUriBuilder.QueryParamCurrentVersion, GetRequestVersion() },
                    { ApiUriBuilder.QueryParamRequestor, this.Requestor },
                });
            requestUri.Path += GetComponentPath(componentToCheck);
            try
            {
                return await JsonLoader.ReadObjectAsync<PackageManifest>(requestUri.Uri);
            }
            catch (Exception ex)
            {
                Trace.TraceWarning("Failure attempting to check for component [{0}] update manifest [{1}]. Details: {2}", componentToCheck, requestUri.ToString(), ex);
            }

            return null;
        }

        public async Task<PackageManifest> GetUpdateVersionAsync(Components componentToCheck, string version)
        {
            var requestUri = new ApiUriBuilder(this.UpdateServiceUri);
            requestUri.Path += String.Join("/", GetComponentPath(componentToCheck), version);
            try
            {
                return await JsonLoader.ReadObjectAsync<PackageManifest>(requestUri.Uri);
            }
            catch (Exception ex)
            {
                Trace.TraceWarning("Failure attempting to check for component [{0}] update manifest [{1}]. Details: {2}", componentToCheck, requestUri.ToString(), ex);
            }

            return null;
        }

        public PackageManifest GetLatestRelease(Components component)
        {
            return GetLatestReleaseAsync(component).Result;
        }

        public async Task<PackageManifest> GetLatestReleaseAsync(Components component)
        {
            var manifests = await GetAvailableManifestsAsync(component);
            if (manifests != null)
            {
                // Manifiests are already sorted by version
                return manifests.FirstOrDefault();
            }
            return null;
        }

        public async Task<IEnumerable<PackageManifest>> GetAvailableManifestsAsync(Components component)
        {
            var requestUri = new ApiUriBuilder(this.UpdateServiceUri, new Dictionary<string, string>
                {
                    { ApiUriBuilder.QueryParamRequestor, this.Requestor },
                });
            requestUri.Path += GetComponentPath(component);
            try
            {
                return (await JsonLoader.ReadObjectAsync<IEnumerable<PackageManifest>>(requestUri.Uri))
                    .OrderByDescending(manifest => manifest.Version);
            }
            catch (Exception ex)
            {
                Trace.TraceWarning("Failure attempting to check for component [{0}] available manifests [{1}]. Details: {2}", component, requestUri.ToString(), ex);
            }
            return null;
        }

        public Uri GetPackageFileUri(Components component, PackageManifest manifest, FilePackage package, string file)
        {
            var requestUri = new ApiUriBuilder(this.UpdateServiceUri, new Dictionary<string, string>
                {
                    { ApiUriBuilder.QueryParamRequestor, this.Requestor },
                });
            requestUri.Path += String.Join("/",
                GetComponentPath(component),
                manifest.Version.GatewayFormat(),
                package.PackageName,
                file);
            return requestUri.Uri;
        }

        public async Task DownloadPackageFileToStreamAsync(Components component, PackageManifest manifest, FilePackage package, string file, Stream destination)
        {
            var fileUri = GetPackageFileUri(component, manifest, package, file);
            try
            {
                using (var downloadClient = new HttpClient())
                {
                    using (var response = await downloadClient.GetAsync(fileUri))
                    {
                        await response.Content.CopyToAsync(destination);
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceWarning("Failed to download package file [{0}] to local stream. Details: {1}", fileUri, ex);
                throw new InvalidOperationException(String.Format("Failed to download package file [{0}] to local stream.", fileUri), ex);
            }
        }

        public async Task<Stream> DownloadPackageFileToLocalFileStreamAsync(Components component, PackageManifest manifest, FilePackage package, string file, string localFilename)
        {
            var retval = File.Create(localFilename);
            await DownloadPackageFileToStreamAsync(component, manifest, package, file, retval);
            retval.Seek(0, SeekOrigin.Begin);
            return retval;
        }

        public async Task<Stream> DownloadPackageFileToTempFileStreamAsync(Components component, PackageManifest manifest, FilePackage package, string file)
        {
            string tempLocation = Path.ChangeExtension(Path.GetTempFileName(), Path.GetExtension(file));
            return await DownloadPackageFileToLocalFileStreamAsync(component, manifest, package, file, tempLocation);
        }

        public async Task DownloadPackageFileToLocalFileAsync(Components component, PackageManifest manifest, FilePackage package, string file, string localFilename)
        {
            var stream = await DownloadPackageFileToLocalFileStreamAsync(component, manifest, package, file, localFilename);
            stream.Dispose();
        }

        public async Task<string> DownloadPackageFileToTempFileAsync(Components component, PackageManifest manifest, FilePackage package, string file)
        {
            string tempLocation = Path.ChangeExtension(Path.GetTempFileName(), Path.GetExtension(file));
            await DownloadPackageFileToLocalFileAsync(component, manifest, package, file, tempLocation);
            return tempLocation;
        }

        static string GetComponentPath(Components component)
        {
            // Possible future decoupling of enumeration & path segment
            return component.ToString();
        }

        static string GetRequestVersion()
        {
            return Assembly.GetCallingAssembly().GetName().Version.GatewayFormat();
        }
    }
}
